# -*- coding : utf-8-*-
import base64
import os
import sys

from PyQt5 import QtCore, QtGui, uic, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import QApplication, QMainWindow, QDesktopWidget, QInputDialog, QMessageBox
import qrcode
from PIL import ImageGrab, Image, ImageQt
import cv2
import numpy as np
from pyzbar import pyzbar
import datetime
import gzip
from io import BytesIO
import wmi
import base64
from pyDes import *
from cacheout import LFUCache


from fencer.fileqrcode_ui import Ui_MainWindow



def capture_screen2(bbox):
    capScr = None
    try:
        capScr = np.array(ImageGrab.grab(bbox))
        capScr = cv2.cvtColor(capScr, cv2.COLOR_RGB2BGR)
    except:
        print("ecaptureScreen error");
    return capScr


def read_into_bytes(filename):
    data = None
    with open(filename, 'rb') as f:
        data = f.read(os.path.getsize(filename))  # f.readinto(buf)
    f.close()
    return data


def gzip_compress(buf):
    out = BytesIO()
    with gzip.GzipFile(fileobj=out, mode="wb") as f:
        f.write(buf)
    return out.getvalue()


def gzip_decompress(buf):
    input = BytesIO(buf)
    with gzip.GzipFile(fileobj=input) as f:
        result = f.read()
    return result


class Decoder(object):
    def __init__(self):
        self.dataArray = None
        self.receiveAll = False
        self.total = 2;
        self.index = 0;
        self.size = 0;
        self.ids = set()
        self.file_name = "NO_NAME"

    def decode(self, str_bytes):
        try:

            part = base64.b64decode(str_bytes)
            part_total = part[0:4]
            total = int.from_bytes(part_total, byteorder='big')
            part_index = part[4:8]
            index = int.from_bytes(part_index, byteorder='big')

            if total < 1 or total <= index:
                self.receiveAll = True
                return

            if self.dataArray is None:  # empty init
                self.dataArray = [None for _ in range(total)]

            if self.dataArray[index] is None:
                self.dataArray[index] = part[8:]
                self.ids.add(index)

            self.total = total
            self.index = index

            size = len(self.ids)
            self.size = size
            if total == size:
                self.receiveAll = True
                self.file_name = bytes.decode(self.dataArray[0])

        except Exception as e:
            self.receiveAll = True
            print("except occur! barcode.data" + str(str_bytes) + " e:\n", e)

    def receive(self, str_bytes):
        if self.receiveAll or str_bytes is None:
            return
        else:
            self.decode(str_bytes)


ext_list = ['.gz', '.7z', '.zip', '.rar', '.exe', '.tar']


class Encoder(object):
    def __init__(self):
        pass

    @staticmethod
    def encode(data_bytes, split_size, file_name):
        ext = os.path.splitext(file_name)[-1]
        need_press = True
        if ext:
            need_press = ext.lower() not in ext_list
        if need_press:
            data_bytes = gzip_compress(data_bytes)
            file_name = "gz1" + file_name
        else:
            file_name = "gz0" + file_name

        total_bytes = len(data_bytes)
        numChunks = int(total_bytes / split_size)
        n = numChunks
        if total_bytes % split_size > 0:
            n = n + 1
        print("encode", total_bytes, n, split_size)

        data_list = []
        total_part = int(n + 1).to_bytes(length=4, byteorder='big', signed=False)
        filename_part = bytes(file_name, encoding="utf8")
        zero_part = int(0).to_bytes(length=4, byteorder='big', signed=False)

        data_list.append(total_part + zero_part + filename_part)  # first index

        for i in range(0, numChunks):
            s_start = i * split_size
            s_end = s_start + split_size
            part = data_bytes[s_start:s_end]
            i_part = int(i + 1).to_bytes(length=4, byteorder='big', signed=False)
            data_list.append(total_part + i_part + part)

        if n > numChunks:
            rest_part = data_bytes[numChunks * split_size:total_bytes]
            rest_i_part = (numChunks + 1).to_bytes(length=4, byteorder='big', signed=False)
            data_list.append(total_part + rest_i_part + rest_part)

        return data_list



class MyDesigner(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MyDesigner, self).__init__(parent)
        self.ids = None
        self.qr_decoder = None
        self.setupUi(self)
        self.btnGen.clicked.connect(self.btn_gen_click)
        self.cb_file.clicked.connect(self.open_file)

        self.btn_recg.clicked.connect(self.rec_click)
        self.btn_lost.clicked.connect(self.find_lost)

        self.btn_money.clicked.connect(self.register)

        self.data_list = None
        self.thread = None
        self.cap = None

        self.cache = LFUCache()

    def create_qrcode(self, txt):
        # 设置二维码

        qr = qrcode.QRCode(
            version=1,
            error_correction=qrcode.constants.ERROR_CORRECT_L,
            box_size=6,
            border=2,
        )
        level = self.cb_level.currentText()
        levels = ['7%', '15%', '25%', '35%']
        levels_int = [1, 0, 3, 2]
        for i in range(0, 4):
            if level == levels[i]:
                qr.error_correction = levels_int[i]
                break
        # print('level', level, qr.error_correction)
        # 添加二维码跳转路径
        qr.add_data(txt)
        # 开启设置二维码颜色
        qr.make(fit=True)
        # 设置二维码颜色
        img = qr.make_image(fill_color="black", back_color="white")
        qimg = ImageQt.ImageQt(img)
        return QPixmap.fromImage(qimg)

    def rec_click(self):
        if self.rb_camera.isChecked():
            self.cap = cv2.VideoCapture(int(self.txt_camera_index.text()))
        zt = self.btn_recg.text()
        if zt == '识别':
            if self.thread is not None and self.thread.isPause:
                self.thread.resume()
            else:
                # 创建线程
                self.qr_decoder = Decoder()
                self.thread = Runthread(-1, -1, int(self.txt_sleep_rec.text().strip()))
                # 连接信号
                self.thread.signal.connect(self.rec_image)  # 进程连接回传到GUI的事件
                # 开始线程
                self.thread.start()

            self.btn_recg.setText('暂停')
        else:
            self.thread.pause()  # 暂停停线程
            self.btn_recg.setText('识别')


    def camera_close(self):
        if self.cap:
            self.cap.release()

    def rec_qrcode(self, img):
        for barcode in pyzbar.decode(img):
            data = barcode.data  # print(myData)
            self.qr_decoder.receive(data)
            # draw qrcode box
            pts = np.array([barcode.polygon], np.int32)
            pts = pts.reshape((-1, 1, 2))
            cv2.polylines(img, [pts], True, (255, 0, 255), 5)
            pts2 = barcode.rect
            tips = str(self.qr_decoder.total) + "/" + str(self.qr_decoder.size) + "/" + str(
                self.qr_decoder.size / self.qr_decoder.total)
            cv2.putText(img, tips, (pts2[0], pts2[1]), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)

            self.label_tip.setText(
                str(self.qr_decoder.total) + "/" + str(self.qr_decoder.size) + "/" + str(self.qr_decoder.index))
            if self.qr_decoder.receiveAll:
                self.thread.cancel()
                self.camera_close()
                print("All data received! save to " + self.qr_decoder.file_name)
                if self.qr_decoder.size == 0:
                    self.txt_rec.setPlainText(data.decode())
                else:
                    out = BytesIO()
                    for i in range(1, len(self.qr_decoder.dataArray)):
                        out.write(self.qr_decoder.dataArray[i])
                    f_bytes = out.getvalue()
                    if self.qr_decoder.file_name.find('gz1') == 0:  # 处理压缩的
                        f_bytes = gzip_decompress(f_bytes)
                    if self.qr_decoder.file_name.find('NO_NAME') > -1:
                        self.txt_rec.setPlainText(bytes.decode(f_bytes))
                    else:
                        file_save_path = os.getcwd() + os.sep + self.qr_decoder.file_name[3:]
                        if os.path.isfile(file_save_path):
                            ext = os.path.splitext(file_save_path)[-1]
                            t = datetime.datetime.now()
                            s = "%s%s%s" % (t.hour, t.minute, t.second)
                            file_save_path = file_save_path + s + ext

                        self.txt_rec.setPlainText("文件保存在:" + file_save_path)
                        with open(file_save_path, 'bw') as f:
                            f.write(f_bytes)

    def find_lost(self):
        self.thread.pause()
        lost_data = []
        for i in range(0, self.qr_decoder.total):
            if i not in self.qr_decoder.ids:
                lost_data.append(i)
        self.txt_rec.setPlainText(str(lost_data).replace("[", "").replace("]", ""))
        self.thread.resume()

    def rec_image(self, msg):
        if msg == -1:
            self.btn_recg.setText("识别")
            return
        image = None
        if self.rb_screen.isChecked():
            rect = self.txt_rect.text().strip()  # .split(",")[:4]
            desktop = QApplication.desktop()
            if rect == '0,0,0,0':
                rect = 0, 0, int(desktop.width() * 0.5), desktop.height()
            else:
                rect = rect.split(",")[:4]
                rect = [int(i) for i in rect]

            image = capture_screen2(rect)
        else:
            success, img = self.cap.read()
            if success:
                img = cv2.resize(img, (480, 320))
                img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
                image = img

        self.rec_qrcode(image)
        showImage = QImage(image.data, image.shape[1], image.shape[0], QImage.Format_RGB888)
        self.label_qrcode.setPixmap(QPixmap.fromImage(showImage))

        if is_reg == False and self.qr_decoder.total > 10:
            self.thread.cancel()
            QMessageBox.information(self, '信息提示对话框', '未注册，接受文件大小超过限制，请注册后再使用!')

    def create_qrcode_batch(self, i):
        real_i = i
        if self.ids is not None:
            real_i = self.ids[i]

        self.lb_qrindex.setText(str(real_i))
        bytes_part = self.data_list[real_i]
        ret = self.cache.get(real_i)
        if ret:
            self.lb_qrcode.setPixmap(ret)
        else:
            pixmap = self.create_qrcode(base64.b64encode(bytes_part))
            self.lb_qrcode.setPixmap(pixmap)
            self.cache.set(real_i, pixmap)

    def open_file(self):
        if self.cb_file.isChecked():
            fileName, fileType = QtWidgets.QFileDialog.getOpenFileName(self, "选取文件", os.getcwd(),
                                                                       "All Files(*);;Text Files(*.txt)")

            if fileName:
                self.lb_filepath.setText(fileName)
                self.txt_input.setPlainText("")
            else:
                self.cb_file.setChecked(False);
        else:
            self.lb_filepath.setText("")

    def btn_gen_click(self):
        zt = self.btnGen.text()
        split_size = int(self.txt_split_size.text().strip())
        if zt == '生成':
            # 创建线程
            data_bytes = None
            start = 0
            if self.cb_file.isChecked():
                data_bytes = read_into_bytes(self.lb_filepath.text())
                (path, filename) = os.path.split(self.lb_filepath.text())
                self.data_list = Encoder.encode(data_bytes, split_size, filename)
            else:
                txt = self.txt_input.toPlainText()
                if len(txt) <= split_size:
                    self.lb_qrnum.setText(str(1))
                    self.lb_qrcode.setPixmap(self.create_qrcode(txt))
                    return
                data_bytes = bytes(txt, encoding="utf8")
                self.data_list = Encoder.encode(data_bytes, split_size, "NO_NAME")

            end = len(self.data_list)

            self.ids = None
            self.lb_qrnum.setText(str(end))
            if self.cb_miss.isChecked():
                ids = self.txt_input_miss.toPlainText()
                ids = ids.split(",")
                ids = [int(i) for i in ids]
                if len(ids) <= 2:
                    start = ids[0]
                    end = ids[1]
                else:
                    self.ids = ids
                    start = 0
                    end = len(ids)
            else:
                self.txt_input_miss.setPlainText(str(start) + "," + str(end))
            self.thread = Runthread(start, end, int(self.txt_sleep_gen.text().strip()))
            # 连接信号
            self.thread.signal.connect(self.create_qrcode_batch)  # 进程连接回传到GUI的事件
            # 开始线程
            self.cache.clear()
            self.thread.start()
            self.btnGen.setText('停止')
        else:
            self.thread.cancel()  # 停止线程
            self.btnGen.setText('生成')


    def register(self):
        text, ok = QInputDialog.getText(self, '验证注册码', '请输入注册码：')
        ok = Register().regist(text)
        if ok:
            self.label_regtip.setText('已注册')
            QMessageBox.information(self, '信息提示对话框', '注册成功')
        else:
            self.label_regtip.setText('注册失败')
            QMessageBox.information(self, '信息提示对话框', '注册失败')


class Runthread(QtCore.QThread):
    # 通过类成员对象定义信号对象
    _signal = pyqtSignal(int)

    def __init__(self, i_start, i_end, sleep_time):
        super(Runthread, self).__init__()
        self.flag = 0
        self.i_start = i_start
        self.i_end = i_end
        self.sleep_time = sleep_time

        self.isPause = False
        self.isCancel = False
        self.cond = QWaitCondition()
        self.mutex = QMutex()

    # 暂停
    def pause(self):
        print("线程暂停")
        self.isPause = True

    # 恢复
    def resume(self):
        print("线程恢复")
        self.isPause = False
        self.cond.wakeAll()

    # 取消
    def cancel(self):
        print("线程取消")
        self.isCancel = True

    # 运行(入口)
    def run(self):
        while not self.isCancel:
            # 线程锁on
            self.mutex.lock()
            if self.isPause:
                self.cond.wait(self.mutex)
            if self.i_start != self.i_end:
                for i in range(self.i_start, self.i_end):
                    if self.isCancel:
                        break
                    self._signal.emit(i)
                    self.msleep(self.sleep_time)
            else:
                if not self.isPause:
                    self._signal.emit(0)
                    self.msleep(self.sleep_time)
            # 线程锁off
            self.mutex.unlock()
        self._signal.emit(-1)

    @property
    def signal(self):
        return self._signal


class Register:
    def __init__(self):
        self.Des_key = "@9527Tjv"  # Key,需八位
        self.Des_IV = "\x11\2\x2a\3\1\x27\2\0"  # 自定IV向量

    # 获取硬件信息，输出macode
    # 1、CPU序列号（ID）  2、本地连接 无线局域网 以太网的MAC  3.硬盘序列号（唯一） 4.主板序列号（唯一）
    global s
    s = wmi.WMI()

    # cpu序列号
    def get_CPU_info(self):
        cpu = []
        cp = s.Win32_Processor()
        for u in cp:
            cpu.append(
                {
                    "Name": u.Name,
                    "Serial Number": u.ProcessorId,
                    "CoreNum": u.NumberOfCores
                }
            )
        return cpu

    # 硬盘序列号
    def get_disk_info(self):
        disk = []
        for pd in s.Win32_DiskDrive():
            disk.append(
                {
                    "Serial": s.Win32_PhysicalMedia()[0].SerialNumber.lstrip().rstrip(),  # 获取硬盘序列号，调用另外一个win32 API
                    "ID": pd.deviceid,
                    "Caption": pd.Caption,
                    "size": str(int(float(pd.Size) / 1024 / 1024 / 1024))
                }
            )
        return disk

    # mac地址（包括虚拟机的）
    def get_network_info(self):
        network = []
        for nw in s.Win32_NetworkAdapterConfiguration():
            if nw.MacAddress != None:
                network.append(
                    {
                        "MAC": nw.MacAddress,
                        "ip": nw.IPAddress
                    }
                )
        return network

    # 主板序列号
    def get_mainboard_info(self):
        mainboard = []
        for board_id in s.Win32_BaseBoard():
            mainboard.append(board_id.SerialNumber.strip().strip('.'))
        return mainboard

    # 由于机器码矿太长，故选取机器码字符串部分字符
    def getCombinNumber(self):
        a = self.get_network_info()
        b = self.get_CPU_info()
        c = self.get_disk_info()
        d = self.get_mainboard_info()
        machinecode_str = ""
        machinecode_str = machinecode_str + a[0]['MAC'] + b[0]['Serial Number'] + c[0]['Serial'] + d[0]

        selectIndex = [3, 5, 8, 10, 15, 16, 17, 30, 32, 38, 43]
        macode = ""
        num = len(machinecode_str)
        for i in selectIndex:
            if i < num:
                macode = macode + machinecode_str[i]

        if macode.find(":") == 0:
            macode = macode[1:]
        return macode

    # DES+base64加密
    def Encryted(self, tr):
        k = des(self.Des_key, CBC, self.Des_IV, pad=None, padmode=PAD_PKCS5)
        EncryptStr = k.encrypt(tr)
        return base64.b32encode(EncryptStr)  # 转base64编码返回

    # 获取注册码，验证成功后生成注册文件
    def regist(self, key):
        # 由于输入类似“12”这种不符合base64规则的字符串会引起异常，所以需要增加输入判断
        key = key.strip()
        if key:
            ontent = self.getCombinNumber()
            tent = bytes(ontent, encoding='utf-8')
            # 得到加密后机器码
            content = self.Encryted(tent)
            key_decrypted = bytes(key, encoding='utf-8')
            if content != 0 and key_decrypted != 0:
                if content == key_decrypted:
                    print("register succeed.")
                    # 读写文件要加判断
                    with open('register.txt', 'w') as f:
                        f.write(key)
                        f.close()
                    return True
        return False

    # 打开程序先调用注册文件，比较注册文件中注册码与此时的硬件信息编码后是否一致
    def checkAuthored(self):
        ontent = self.getCombinNumber()
        tent = bytes(ontent, encoding='utf-8')
        # 获得加密后的机器码
        content = self.Encryted(tent)
        # 读写文件要加判断
        try:
            f = open('register.txt', 'r')
            if f:
                key = f.read()
                if key:
                    key_decryted = bytes(key.strip(), encoding='utf-8')
                    if key_decryted == content:
                        return True
        except Exception as e:
            print(e)
            return False


is_reg = False
if __name__ == "__main__":
    app = QApplication(sys.argv)
    ui = MyDesigner()
    ui.move(QDesktopWidget().availableGeometry().center())
    ui.setFixedSize(920, 665)
    try:
        reg = Register()
        is_reg = reg.checkAuthored()
    except Exception as e:
        print(e)
    if is_reg:
        ui.label_regtip.setText('已注册')
    ui.label_macode.setText(reg.getCombinNumber())
    ui.lb_qrcode.setScaledContents(True)
    ui.label_qrcode.setScaledContents(True)
    ui.show()
    sys.exit(app.exec_())
